package org.dromara.oce.service.remote.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.http.client.utils.URIBuilder;
import org.dromara.common.core.enums.DimensionsEnum;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.oce.config.DsdjConfig;
import org.dromara.oce.config.JuLiangConfig;
import org.dromara.oce.config.JuLiangProperties;
import org.dromara.oce.constant.PlatformJuLangConstants;
import org.dromara.oce.constant.RedisKeyUtils;
import org.dromara.oce.domain.dto.*;
import org.dromara.oce.service.remote.OceanEngineRemoteService;
import org.dromara.oce.utils.DateTimeUtil;
import org.dromara.oce.utils.PlatformConstants;
import org.dromara.oce.utils.TimeUtils;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class OceanEngineRemoteServiceImpl implements OceanEngineRemoteService {

    private final JuLiangConfig juLiangConfig;
    private final DsdjConfig dsdjConfig;

    /**
     * 获取接口调用的凭证 client_token。用于巨量授权 (目前调用老系统统一获取)
     *
     * @return token
     */
    @Override
    public String getClientToken() {
        String appCode = MDC.get("appCode");
        return getClientToken(appCode);
    }

    @Override
    public String getClientToken(String appCode) {
        JuLiangProperties juLiangProperties = juLiangConfig.getJuLiangConfigList().stream().filter(item -> item.getAppCode().equals(appCode)).findFirst().get();
        String key = RedisKeyUtils.getAccessTokenKey(PlatformJuLangConstants.JULIANG, juLiangProperties.getAppId());
        //从缓存获取token
        String accessToken = RedisUtils.getCacheObject(key);
        if (StringUtils.isNotBlank(accessToken)) {
            return accessToken;
        }
        //如果缓存有refreshtoken那么就用他刷新accesstoken
        String ref = RedisKeyUtils.getRefreshTokenKey(PlatformConstants.JULIANG, juLiangProperties.getAppId());
        String refresh_token = RedisUtils.getCacheObject(ref);
        if (StringUtils.isBlank(refresh_token)) {
            log.warn("RefreshToken为空");
            return null;
        }
        return refreshClientToken(appCode, refresh_token);
    }

    @Override
    public String refreshClientToken(String appCode, String refresh_Token) {
        JuLiangProperties juLiangProperties = juLiangConfig.getJuLiangConfigList().stream().filter(item -> item.getAppCode().equals(appCode)).findFirst().get();
        String key = RedisKeyUtils.getAccessTokenKey(PlatformConstants.JULIANG, juLiangProperties.getAppId());
        String ref = RedisKeyUtils.getRefreshTokenKey(PlatformConstants.JULIANG, juLiangProperties.getAppId());
        //获取新的accesstoken
        JSONObject json = new JSONObject();
        json.put("appid", juLiangProperties.getAppId());
        json.put("secret", juLiangProperties.getSecret());
        json.put("grant_type", "refresh_token");
        json.put("refresh_token", refresh_Token);
        //发送post请求
        log.info("刷新Refresh Token接口请求参数：{}", JSONUtil.toJsonStr(json));
        String post = HttpUtil.post(juLiangConfig.getUrl() + "/open_api/oauth2/refresh_token/", JSONUtil.toJsonStr(json));
        log.info("刷新Refresh Token接口返回参数：{}", post);
        JSONObject resultObject = JSONObject.parseObject(post);
        int code = resultObject.getIntValue("code");
        String message = resultObject.getString("message");
        if (code != 0) {
            log.error("《巨量》刷新token失败，code={}, message={}", code, message);
            throw new ServiceException("《巨量》刷新token失败:" + message);
        }
        //获取data
        JSONObject data = resultObject.getJSONObject("data");
        if (data == null) {
            throw new ServiceException("《巨量》响应中未找到 'data' 字段");
        }
        String accessToken = data.getString("access_token");
        log.info("《巨量》accessToken:{}", accessToken);
        String refreshToken = data.getString("refresh_token");
        log.info("《巨量》refreshToken:{}", refreshToken);
        // token过期时间
        RedisUtils.setCacheObject(key, accessToken, Duration.ofSeconds(data.getLong("expires_in") - 200L));
        RedisUtils.setCacheObject(ref, refreshToken, Duration.ofSeconds(data.getLong("refresh_token_expires_in") - 200L));
        return accessToken;
    }

    /**
     * 获取三方接口调用的凭证 client_token。用于巨量小程序拉取iaa数据的授权 (目前调用老系统统一获取)
     *
     * @return token
     */
    @Override
    public String getAppClientToken() {
        String appCode = MDC.get("appCode");
        String key = RedisKeyUtils.getAppClientTokenKey(appCode);
        //从缓存获取token
        String accessToken = RedisUtils.getCacheObject(key);
        if (StringUtils.isBlank(accessToken)) {
            String url = dsdjConfig.getDsdjServerReqeustUrl() + "/app/third/getClientToken?platform=" + PlatformJuLangConstants.DOUYIN + "&appCode=" + appCode;
            accessToken = HttpRequest.get(url).execute().body();
            if (StringUtils.isNotBlank(accessToken)) {
                RedisUtils.setCacheObject(key, accessToken, Duration.ofMinutes(5L));
            }
        }
        return accessToken;
    }

    /**
     * 获取广告主信息
     *
     * @param advertiserId 广告主id
     * @return
     */
    @Override
    public GetAdvertiserInfo getAdvertiserInfo(Long advertiserId) {
        log.info("《巨量》获取广告主信息，请求参数：{}", advertiserId);
        try {
            String param = JSONUtil.toJsonStr(Collections.singletonList(advertiserId));
            String result = HttpRequest.get(juLiangConfig.getUrl() + "/open_api/2/advertiser/info/?advertiser_ids=" + URLEncoder.encode(param, "UTF-8"))
                .header("Access-Token", getClientToken()).execute().body();
            JSONObject jsonObject = JSON.parseObject(result);
            int code = jsonObject.getInteger("code");
            if (code != 0) {
                log.error("《巨量》获取广告主信息失败，code={}, message={}", code, jsonObject.getString("message"));
                return null;
            }
            log.info("《巨量》获取广告主信息，响应报文:{}", result);
            String data = jsonObject.getString("data");
            JSONArray resultArray = JSONArray.parseArray(data);
            List<GetAdvertiserInfo> getAdvertiserInfos = BeanUtil.copyToList(resultArray, GetAdvertiserInfo.class);
            if (CollectionUtils.isNotEmpty(getAdvertiserInfos)) {
                String appCode = MDC.get("appCode");
                GetAdvertiserInfo getAdvertiserInfo = getAdvertiserInfos.get(0);
                getAdvertiserInfo.setAppCode(appCode);
                return getAdvertiserInfo;
            }
            // 返回处理后的数据
            return null;
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("《巨量》获取广告主信息异常");
        }
    }

    /**
     * 素材数据
     */
    @Override
    public List<GetVideoReportDTO> getVideoReport(String advertiserId, String startDate, String endDate) {
//        log.info("《巨量》获取素材数据报表，请求参数advertiserId={}，startDate={},endDate={}", advertiserId, startDate, endDate);
        if (ObjectUtils.isEmpty(advertiserId)) return null;
        // 定义需要的维度和指标
//        List<String> dimensions = Arrays.asList("stat_time_day,cdp_project_id,cdp_promotion_id,cdp_promotion_name,cdp_project_name,material_id,ad_platform_material_name".split(","));
        List<String> dimensions = Arrays.asList("stat_time_day,material_id,ad_platform_material_name".split(","));
        List<String> metrics = Arrays.asList("stat_cost,show_cnt,cpm_platform,click_cnt,ctr,cpc_platform,convert_cnt,conversion_cost,conversion_rate,deep_convert_cnt,deep_convert_cost,deep_convert_rate,active,active_cost,active_rate,active_pay,active_pay_cost,active_pay_rate,game_pay_count,game_pay_cost,total_play,valid_play,valid_play_cost,valid_play_rate,play_duration_3s,valid_play_of_mille,valid_play_cost_of_mille,play_25_feed_break,play_50_feed_break,play_75_feed_break,play_99_feed_break,average_play_time_per_play,play_over_rate,wifi_play_rate,card_show,dy_like,dy_comment,dy_share,dislike_cnt,report_cnt,ies_challenge_click,ies_music_click,location_click".split(","));
        JSONObject parama = new JSONObject();
        parama.put("advertiser_id", advertiserId);
        parama.put("data_topic", "MATERIAL_DATA");
        parama.put("dimensions", dimensions);
        parama.put("metrics", metrics);
        parama.put("start_time", startDate);
        parama.put("end_time", startDate);
        parama.put("page", 1);
        parama.put("page_size", 100);

        JSONObject filter = new JSONObject();//查询消耗大于0的素材
        filter.put("field", "stat_cost");
        filter.put("type", 2L);
        filter.put("operator", 4L);
        filter.put("values", Arrays.asList("0"));
        parama.put("filters", List.of(filter));

        JSONObject orderBy = new JSONObject();
        orderBy.put("field", "cdp_promotion_id");
        orderBy.put("type", "ASC");
        parama.put("order_by", List.of(orderBy));
        try {
            // 从缓存中获取
            long num = 100L;
            String key = "000000:sys_config";
            Map<String, String> map = RedisUtils.getCacheMap(key);
            if (CollectionUtils.isNotEmpty(map)) {
                num = Long.parseLong(map.getOrDefault("oce.sleep.num", "100"));
            }
            Thread.sleep(num);
            long startTime = System.currentTimeMillis();
            String result = get(juLiangConfig.getUrl() + "/open_api/v3.0/report/custom/get/", this.getClientToken(), parama.toJSONString());
            JSONObject jsonObject = JSONObject.parseObject(result);
            if (jsonObject.getInteger("code") != 0) {
                result = get(juLiangConfig.getUrl() + "/open_api/v3.0/report/custom/get/", this.getClientToken(), parama.toJSONString());
                jsonObject = JSONObject.parseObject(result);
                if (jsonObject.getInteger("code") != 0) {
                    log.warn("《巨量》获取素材数据报表，失败，code={}, message={}", jsonObject.getInteger("code"), jsonObject.getString("message"));
                    return null;
                }
            }
            JSONObject data = jsonObject.getJSONObject("data");
            JSONArray rows = data.getJSONArray("rows");
            long endTime = System.currentTimeMillis();
            log.info("《巨量》获取巨量广告主={},日期：{} 获取素材数据报表数量{},耗时：{}毫秒", advertiserId, startDate, rows.size(), endTime - startTime);
            List<GetVideoReportDTO> resp = new ArrayList<>();
            for (Object row : rows) {
                JSONObject row1 = (JSONObject) row;
                GetVideoReportDTO dto = BeanUtil.copyProperties(row1.getJSONObject("dimensions"), GetVideoReportDTO.class);
                BeanUtil.copyProperties(row1.getJSONObject("metrics"), dto);
                dto.setTimestamps(DateTimeUtil.getTimestampSecond(startDate));
                dto.setThatDayDate(dto.getStatTimeDay());
                dto.setThirdAdvertiserId(advertiserId);
                resp.add(dto);
            }
            return resp;
        } catch (Exception e) {
            log.error("《巨量》获取素材数据报表异常", e);
        }
        return null;
    }

    /**
     * 查询广告主下面的巨量广告列表
     *
     * @param advertiserId 广告主
     * @param createDate   创建时间
     */
    @Override
    public GetPromotionNumDTO getPromotionNum(String advertiserId, String createDate) {
        JSONObject params = new JSONObject();
        params.put("advertiser_id", advertiserId);
        JSONObject filter = new JSONObject();
        filter.put("promotion_create_time", createDate);
        params.put("filtering", filter);
        params.put("page", 1);
        params.put("page_size", 1);
        int totalNum = 0;
        GetPromotionNumDTO resp = null;
        try {
            long startTime = System.currentTimeMillis();
            String result = get(juLiangConfig.getUrl() + "/open_api/v3.0/promotion/list/", this.getClientToken(), params.toJSONString());
            JSONObject resultObject = JSONObject.parseObject(result);
            if (resultObject.getIntValue("code") != 0) {
                result = get(juLiangConfig.getUrl() + "/open_api/v3.0/promotion/list/", this.getClientToken(), params.toJSONString());
                resultObject = JSONObject.parseObject(result);
                if (resultObject.getIntValue("code") != 0) {
                    log.warn("获取广告列表失败，{}", resultObject.getString("message"));
                    return resp;
                }
            }
            JSONObject data = resultObject.getJSONObject("data");
            JSONArray list = data.getJSONArray("list");
            long endTime = System.currentTimeMillis();
            log.info("《巨量》获取巨量广告主={},日期：{} 获取广告列表数量{},耗时：{}毫秒", advertiserId, createDate, list.size(), endTime - startTime);
            List<PromotionListV3Response> listDTO = BeanUtil.copyToList(list, PromotionListV3Response.class);
            resp = new GetPromotionNumDTO();
            totalNum = CollectionUtils.isNotEmpty(listDTO) ? data.getJSONObject("page_info").getInteger("total_number") : 0;
            resp.setPromotionNum(totalNum);
            resp.setThatdayDate(createDate);
            resp.setThatdayTimestamp(DateTimeUtil.getTimestampSecond(createDate));
        } catch (Exception e) {
            log.error("获取广告列表异常", e);
        }
        return resp;
    }

    /**
     * 查询广告收益
     *
     * @return
     */
    @Override
    public List<DyEcpmCostDTO> queryAdvertAmount(long startTime, long endTime, String appCode) {
        List<DyEcpmCostDTO> respList = new ArrayList<>();
        if (StringUtils.isBlank(appCode)) {
            return respList;
        }
        MDC.put("appCode", appCode);
        long cursor = 0;
        JSONObject req = new JSONObject();
//        req.put("cursor", cursor + "");
        req.put("start_date", startTime + "");
        req.put("end_date", endTime + "");
        req.put("date_hour", "");
        req.put("open_id", "");
        req.put("page_size", 500);
        do {
            req.put("cursor", cursor + "");
            String result = HttpRequest.post("https://open.douyin.com/api/traffic/v2/rt_ecpm/query/")
                .header("access-token", getAppClientToken())
                .body(JSONUtil.toJsonStr(req)).execute().body();
//          String result = "{\"data\":{\"records\":[{\"id\":\"180\",\"mp_id\":\"tt123\",\"cost\":\"100\",\"open_id\":\"open_id_123\",\"event_time\":\"1698813481\"}],\"next_cursor\":\"200\"},\"err_no\":\"0\",\"err_msg\":\"success\",\"log_id\":\"202311301018036FC052BE3090CB55F5F1\"}";
            log.info("《抖音》广告实时数据下发，返回参数：{}", result);
            DResult dResult = JSONUtil.toBean(result, DResult.class);
            if (0 != dResult.getErr_no()) {
                log.info("《抖音》广告实时数据下发,{}", dResult.getErr_msg());
                cursor = -1;
            }
            cursor = dResult.getData().getLongValue("next_cursor");
            JSONArray records = dResult.getData().getJSONArray("records");
            if (records != null && !records.isEmpty()) {
                List<DyEcpmCostDTO> list = BeanUtil.copyToList(records, DyEcpmCostDTO.class);
                respList.addAll(list);
            } else {
                cursor = -1;
            }
        } while (cursor != -1);
        return respList;
    }

    @Override
    public List<GetAdvertiserReportDTO> getAdvertiserReportHours(String advertiserId, String appCode, String startDate) {
        if (StringUtils.isEmpty(advertiserId)) return null;
        MDC.put("appCode", appCode);
        String accessToken = getClientToken();
        List<GetAdvertiserReportDTO> advertiserReportList = new ArrayList<>();
        try {
            // 从缓存中获取
            long num = 100L;
            String key = "000000:sys_config";
            Map<String, String> map = RedisUtils.getCacheMap(key);
            if (CollectionUtils.isNotEmpty(map)) {
                num = Long.parseLong(map.getOrDefault("oce.sleep.num", "100"));
            }
            Thread.sleep(num);
            long startTime = System.currentTimeMillis();
            List<String> fields = Arrays.asList(("cost,show,avg_show_cost,click,avg_click_cost,ctr,convert,convert_cost,convert_rate,deep_convert,deep_convert_cost,deep_convert_rate," +
                "active,active_cost,active_rate,register,active_register_cost,active_register_rate,pay_count,active_pay_cost,active_pay_rate,game_pay_cost,game_pay_count,attribution_micro_game_0d_ltv,attribution_micro_game_0d_roi").split(","));
            JSONObject params = new JSONObject();
            params.put("advertiser_id", advertiserId);
            params.put("start_date", startDate);
            params.put("end_date", startDate);
            params.put("fields", fields.toArray());
            params.put("time_granularity", "STAT_TIME_GRANULARITY_HOURLY");
            params.put("page_size", 100);
            String result = get(juLiangConfig.getAdvertiserUrl() + "/report/advertiser/get/", accessToken, params.toJSONString());
            JSONObject jsonObject = JSONObject.parseObject(result);
            long endTime = System.currentTimeMillis();
            log.info("《巨量》获取巨量广告主={},日期：{} 每小时数据,耗时：{}毫秒", advertiserId, startDate, endTime - startTime);
            if (jsonObject.getInteger("code") != 0) {
                result = get(juLiangConfig.getAdvertiserUrl() + "/report/advertiser/get/", accessToken, params.toJSONString());
                jsonObject = JSONObject.parseObject(result);
                if (jsonObject.getInteger("code") != 0) {
                    log.warn("获取《巨量》获取巨量广告主每小时数据失败，code={}, message={}", jsonObject.getInteger("code"), jsonObject.getString("message"));
                    return null;
                }
            }
            JSONObject data = jsonObject.getJSONObject("data");
            JSONArray list = data.getJSONArray("list");
            if (list == null) {
                log.warn("《巨量》广告主{}暂时没有数据", advertiserId);
                return null;
            }
//            log.info("《巨量》获取巨量广告主={}，日期：{} 每小时数据 {}", advertiserId, startDate, list);
            for (Object cost : list) {
                GetAdvertiserReportDTO reportDTO = BeanUtil.copyProperties(cost, GetAdvertiserReportDTO.class);
                reportDTO.setTimestamps(TimeUtils.getTimestampSecond(startDate));
                reportDTO.setThatDayDate(startDate);
                reportDTO.setThatdayHoursTimestamp(TimeUtils.getHoursTimestamp(reportDTO.getStatDatetime()));
                reportDTO.setThirdAdvertiserId(advertiserId);
                reportDTO.setActivePay(reportDTO.getPayCount());
                advertiserReportList.add(reportDTO);
            }
            if (!advertiserReportList.isEmpty()) {
                advertiserReportList = advertiserReportList.stream().sorted(Comparator.comparing(GetAdvertiserReportDTO::getThatdayHoursTimestamp)).collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("《巨量获取》广告主{}获取信息异常", advertiserId, e);
        }
        return advertiserReportList;
    }

    @Override
    public List<GetCustomReportDTO> getCustomReport(String advertiserId, String appCode, String startDate, String endDate, DimensionsEnum dimensionsEnum) {
        if (StringUtils.isEmpty(advertiserId)) return null;
        MDC.put("appCode", appCode);
        String accessToken = getClientToken();

        // 定义需要的维度和指标
        List<String> dimensions = Arrays.asList("stat_time_day", dimensionsEnum.getDimensions());
        List<String> metrics = Arrays.asList(dimensionsEnum.getMetrics().split(","));

        JSONObject parama = new JSONObject();
        parama.put("advertiser_id", advertiserId);
        parama.put("data_topic", "BASIC_DATA");
        parama.put("dimensions", dimensions);
        parama.put("metrics", metrics);
        parama.put("start_time", startDate);
        parama.put("end_time", endDate);
        parama.put("page", 1);
        parama.put("page_size", 100);

        JSONObject filter = new JSONObject();
        filter.put("field", "stat_cost");
        filter.put("type", 3);
        filter.put("operator", 4);
        filter.put("values", Arrays.asList("0"));
        parama.put("filters", Arrays.asList(filter));
        parama.put("order_by", Arrays.asList());

        try {
            String result = get(juLiangConfig.getUrl() + "/open_api/v3.0/report/custom/get/", accessToken, parama.toJSONString());
            log.info("《巨量》自定义数据报表，返回参数：{}", result);
            JSONObject jsonObject = JSONObject.parseObject(result);
            if (jsonObject.getInteger("code") != 0) {
                log.debug("《巨量》自定义数据报表，失败，code={}, message={}", jsonObject.getInteger("code"), jsonObject.getString("message"));
                throw new ServiceException("自定义数据报表失败");
            }

            JSONObject data = jsonObject.getJSONObject("data");
            JSONArray rows = data.getJSONArray("rows");
            List<GetCustomReportDTO> resp = new ArrayList<>();
            for (Object row : rows) {
                JSONObject row1 = (JSONObject) row;
                GetCustomReportDTO dto = new GetCustomReportDTO();
                BeanUtil.copyProperties(row1.getJSONObject("metrics"), dto);

                JSONObject metrics1 = row1.getJSONObject("dimensions");
                dto.setDimensionsType(dimensionsEnum.getDimensions());
                dto.setDimensionsName(metrics1.getString(dimensionsEnum.getDimensions()));
                dto.setStatTimeDay(metrics1.getString("stat_time_day"));

                dto.setThatdayTimestamp(DateTimeUtil.getTimestampSecond(startDate));
                dto.setThatdayDate(dto.getStatTimeDay());
                dto.setThirdAdvertiserId(advertiserId);
                resp.add(dto);
            }
            return resp;
        } catch (Exception e) {
            log.info("《巨量》自定义数据报表异常,{}", e);
        }
        return null;
    }

    /**
     * API回传
     *
     * @param req
     * @return
     */
    @Override
    public boolean reportBack(OceanEngineReport req) {
        OceanEngineReportReqVO reqVO = new OceanEngineReportReqVO();
        reqVO.setEventType(req.getEventTypeEnums().getType());

        OceanEngineReportReqVO.Context context = new OceanEngineReportReqVO.Context();

        OceanEngineReportReqVO.Ad ad = new OceanEngineReportReqVO.Ad();
        ad.setCallback(req.getCallback());
        context.setAd(ad);

        OceanEngineReportReqVO.Device device = new OceanEngineReportReqVO.Device();
        device.setPlatform(req.getPlatform());
        device.setImei(req.getImei());
        device.setIdfa(req.getIdfa());
        device.setOaid(req.getOaid());
        context.setDevice(device);

        if (!ObjectUtils.isEmpty(req.getPayAmount())) {
            OceanEngineReportReqVO.Properties properties = new OceanEngineReportReqVO.Properties();
            properties.setPayAmount(req.getPayAmount());
            properties.setConvTime(req.getConvTime());
            properties.setOrderId(req.getOrderId());
            context.setProperties(properties);
        }
        context.setTimestamp(req.getTimestamp());
        context.setOuterEventId(req.getOuterEventId());
        reqVO.setContext(context);

        String reqStr = JSON.toJSONString(reqVO);
        log.info("《巨量》API回传请求参数：{}", reqStr);
        String result = HttpUtil.post("https://analytics.oceanengine.com/api/v2/conversion", reqStr);
        log.info("《巨量》API回传请求响应：{}", result);
        JSONObject resultObject = JSONObject.parseObject(result);
        int code = resultObject.getIntValue("code");
        String message = resultObject.getString("message");
        if (code != 0) {
            log.error("《巨量》API回传失败，code={}, message={}", code, message);
            return false;
        }
        return true;
    }

    /**
     * Send GET request
     *
     * @param jsonStr:Args in JSON format
     * @return Response in JSON format
     */
    private static String get(String reqUrl, String token, String jsonStr) throws IOException, URISyntaxException {
        OkHttpClient client = new OkHttpClient().newBuilder().build();
        URIBuilder ub = new URIBuilder(reqUrl);
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> map = mapper.readValue(jsonStr, Map.class);
        map.forEach((k, v) -> {
            try {
                ub.addParameter(k, v instanceof String ? (String) v : mapper.writeValueAsString(v));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        });
        URL url = ub.build().toURL();

        Request request = new Request.Builder()
            .url(url)
            .method("GET", null)
            .addHeader("Access-Token", token)
            .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
        } catch (SocketTimeoutException e) {
            response = client.newCall(request).execute();
        }
        return response.body().string();
    }

    /**
     * Build request URL
     *
     * @param path Request path
     * @return Request URL
     */
    private static String buildUrl(String path) throws URISyntaxException {
        URI uri = new URI("https", "ad.oceanengine.com", path, "", "");
        return uri.toString();
    }

}
